#include<iostream>
#include<cstdlib>
#include<cassert>
#include<unistd.h>
#include<ctime>
#include<string>
#include<cstdio>
#include<vector>
#include <sys/types.h>
#include <sys/wait.h>

using namespace std;
// 创建进程池，首先指定创建的个数
#define PROCESS_NUM 5

//方法的函数指针，用于表示无返回值，无参数名为func_t的函数指针类型。
typedef void (*func_t)();

//创建用于描述子进程的数据结构
class subEp
{
public:
    //构造函数，需要子进程的pid，对应开启的写端,注意：此时存入的写端是父进程的写端，因为需要实现单相通信
    subEp(const pid_t& pid, const int& wfd)
        :_pid(pid), _wfd(wfd)
    {
        //生成当前子进程名称,用snprintf 向中介字符串按格式打入字符
        char buffer[1024];
        snprintf(buffer,sizeof buffer,"进程编号-> %d pid->[%d]",_num++,_pid);
        _name = buffer;
    }
    string _name;
    //进程的PID
    pid_t _pid;
    //打开的写端口
    int _wfd;
    //静态计数变量，用于动态更改子进程结构的名字
    static int _num;
};
//类外初始化静态成员函数
int subEp:: _num = 0;



void LoadTask()
{
    cout<<"正在执行 <加载> 任务"<<endl;
    sleep(1);
}

void SendTask()
{
    cout<<"正在执行 <发送> 任务"<<endl;
    sleep(1);
}

void UPTask()
{
    cout<<"正在执行 <上传> 任务"<<endl;
    sleep(1);
}

void GameTask()
{
    cout<<"正在执行 <游戏> "<<endl;
    sleep(1);
}




//做随机数种子
void MakeSeed()
{
    srand((unsigned long)time(nullptr) ^ getpid() ^ 0x171237 ^ rand() % 1234);
}


//加载方法表,传入容器
void loadTaskFunc(vector<func_t>& vfunc)
{
    //把所有的方法函数传入容器中
    vfunc.push_back(GameTask);
    vfunc.push_back(UPTask);
    vfunc.push_back(LoadTask);
    vfunc.push_back(SendTask);

}

int GetTask(int readfd)
{
    //有了对应的fd即可使用read函数读取到指定的任务码了
    //由于我们使用随机数来分发任务码，而read返回的ssize_t会返回读取到的字节数，所以我们需要返回的值等于4

    //read的第二个参数是一个void*，我们需要传入对应的数据类型的地址。
    int code = 0;
    ssize_t ret = read(readfd,&code,sizeof(int)) ;
    if(ret == 4) return code;
    else if(ret <= 0) return -1;
    else return 0;

}


void createSubProcess(vector<subEp> &subs, vector<func_t> &funcMap)
{
        // 创建进程以及管道
    vector<int> DelFd;
    for(int i = 0; i < PROCESS_NUM; ++i)
    {
        //先创建管道文件，管道的函数是一个输出型参数，创建一个数组用于存储
        int fds[2];
        int check = pipe(fds);
        assert(check == 0);
        //由于在检测完assert后，check变量没有使用的必要，将其void

        //创建子进程
        pid_t pid = fork();
        if(pid == 0)
        {
            //子进程进行的工作
            //关掉子进程的写端

            for(int i = 0; i < DelFd.size();++i)
            close(DelFd[i]);

            close(fds[1]);

            while(true)
            {
                //走到这里，子进程等等待父进程发送消息，使用管道来获取父进程的消息
                //我们单独编写一个函数来从对应的管道读取父进程的消息。
                //把读端传入函数内部
                int CommandCode = GetTask(fds[0]);

                //拿取到了对应的任务码，就执行对应的任务

                //检查一下返回值是否合法,如果是0，那么就是没有任务了，结束循环。
                if (CommandCode >= 0 && CommandCode < funcMap.size())
                funcMap[CommandCode]();
                else if(CommandCode == -1)  break;
            }

            //子进程退出
            exit(0);

        }
        close(fds[0]);

        //在这里，父进程要做的是将各个已经启动的子进程放入对应的进程属性表内。
        subEp proc(pid,fds[1]);
        subs.push_back(proc);
        //为了动态保持名字，num还需要++
        //在创建子进程的期间，之前打开的写端也会因为多几轮的创建子进程继承到子进程内部，虽然子进程不会使用，但是还是处理掉为妙
        //使用一个容器存放对应额外的写端fd
        DelFd.push_back(fds[1]);

    }
}

//发送任务，给指定的子进程发送指定的任务码
void SendTask(subEp proc, int tasknum)
{
    cout<<"已将任务："<<tasknum<<"发送给"<<proc._name<<"对应PID:"<<proc._pid<<endl;
    int ret = write(proc._wfd,&tasknum,sizeof(tasknum)) ;
    assert(ret == sizeof(int));
    // void(ret);

}


//到了这一步，子进程已经具有完成了接受任务并且可以运行的能力，接下来的这个函数编写，则是站在父进程的角度对子进程发任务
//count确定了本次任务需要发几轮,如果是-1就一直发。
void loadBlanceContrl( vector<subEp> subs, vector<func_t> funcMap,int count)
{

    int tasknum = funcMap.size();
    //随机发送任务。还需要对应的send函数
    if(count != -1)
        for(int i = 0; i < count ; ++ i)    
        {
                //先将需要发送的子进程对象以及对应随机的任务的随机数都准备好
            int Procrand = rand() % PROCESS_NUM;
            cout<<"控制进程的随机数："<<Procrand << endl;

            int Taskrand = rand() %  tasknum;
            SendTask(subs[Procrand], Taskrand);
            sleep(1);
        }
    else
        while(1)
        {
            int Procrand = rand() % PROCESS_NUM;
            cout<<"控制进程的随机数："<<Procrand << endl;

            int Taskrand = rand() %  tasknum;
             SendTask(subs[Procrand], Taskrand);
             sleep(1);
        }    

    //关闭对应的写端
    for(int i = 0; i <PROCESS_NUM; i++) close(subs[i]._wfd); // waitpid();

}
 void waitProcess(vector<subEp> subs)
{
    //等待所有的子进程退出
    for(int i = 0; i < subs.size();++i)
    {
        waitpid(subs[i]._pid,nullptr,0);
        cout<<"等待子进程退出"<<endl;
    }
}
int main()
{
    MakeSeed();
    // 1. 建立子进程并建立和子进程通信的信道, 有bug的，但是不影响我们后面编写
    // 1.1 加载方法表
    vector<func_t> funcMap;
    loadTaskFunc(funcMap);
    // 1.2 创建子进程，并且维护好父子通信信道
    vector<subEp> subs;
    createSubProcess(subs, funcMap);

    // 2. 走到这里就是父进程, 控制子进程，负载均衡的向子进程发送命令码
    int taskCnt = -1; // -1: 永远进行
    loadBlanceContrl(subs, funcMap, taskCnt);

    // 3. 回收子进程信息
    waitProcess(subs);
 
    return 0;
}
